{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "36b496da",
   "metadata": {},
   "source": [
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/state-reducers.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239428-lesson-2-state-reducers)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7ae0ff7-497d-4c31-a57a-00fe92799232",
   "metadata": {},
   "source": [
    "# State Reducers \n",
    "\n",
    "## Review\n",
    "\n",
    "We covered a few different ways to define LangGraph state schema, including `TypedDict`, `Pydantic`, or `Dataclasses`.\n",
    " \n",
    "## Goals\n",
    "\n",
    "Now, we're going to dive into reducers, which specify how state updates are performed on specific keys / channels in the state schema."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "398c5e8e-641f-4be6-b1e8-7531f86bd2e9",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install --quiet -U langchain_core langgraph"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d5bd534-c5be-48fe-91bc-af39ebee76b7",
   "metadata": {},
   "source": [
    "## Default overwriting state\n",
    "\n",
    "Let's use a `TypedDict` as our state schema."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "64e2438c-9353-4256-bc3c-1bb830374c0b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGsAAADqCAIAAAAqMSwmAAAAAXNSR0IArs4c6QAAFXxJREFUeJztnXtcE1e+wE8yk5B3CAmEpzxERAXRitRaH1jRqqUoasUHttrWrbtu7+dT7W73dtm1vW1dF23rva3VttI31VqrUtT1VaUVXarUPqCgFFARwiPvhLxnkvsHXuSWJDPJJORA5/uXzpwz+fHNmZMz55w5h+FyuQANBZihDmDYQxukCm2QKrRBqtAGqUIbpApKMb9R49CrHWYjbjbgmMPldA6DthGCAhRl8kQIT4hKolk8ASUJDP/ag+pOW8tPpht1JjaPAVwMnhDhiRAuH3Xiw8AgymL0GjCzATcbMZvFyWIzUzL5qVkCkZTlx9V8Ntirwy5VqlwAhMtYyZn8qHiOH58KFZ03LK11Jm23XSBBp+fL2BzfajbfDF45ram/pJ/+sGzsFKHvocJOXbX+0jHVtIekWTPDyefywWDFno7UyYIJ08T+Rjg8+O6sRt1ln18cTTI92RJb9rcbkx+QjHh9AIApeRGJ6fyKPR1kM7hIsK+kVaWwkkk5YvjlB+OBnW1kUhLfxRV7OiY/IBk1lheA73dY0fitoaPVkrdK7j0ZgcHaMxquAJlw38i/ed1Se1bD5RP8+d7qwV4dVndR/5vVBwDIzos4f1DpPY03g5cqVdMflgU6qmHGffnSS5UqLwk8GlR32lwAjMh2n09MmStRKWxWE+YpgUeDLT+ZwmX+POX4R319vc1mC1V27/BFaGu92dNZjwZv1JmSM/lBiulXVFZWrlu3zmKxhCQ7ISmZgta6Xk9n3Rs0aBxhPOaQPfP6XXz6GhLBK319JGfwe7WYp24nDwbVjiAN4d26dWvjxo0zZsxYtGjRtm3bnE5nZWXl9u3bAQB5eXnZ2dmVlZUAgO7u7q1bt+bl5U2bNq2oqOjkyZN92XU6XXZ29scff1xSUjJjxowNGza4zR5wMIdLr3K4PeW+a8xsxHlCJBihvPTSSzdv3tyyZYvJZKqtrWUymffff39xcfEnn3yya9cugUAwatQoAACGYT///PPy5cvDw8PPnTtXUlKSkJAwYcKEvouUlZU98sgje/fuRRBELpcPzh5weCLEbMAlUW5OeTBowHmioBhUKBTp6emFhYUAgOLiYgBAREREfHw8ACAjIyM8/E6nSFxc3Oeff85gMAAAixcvzsvLq6qq6jeYmZm5adOm/msOzh5w+CLUZHD/c+zxl4TFDsoAwKJFi2pqakpLSzUajfeUTU1NmzdvXrBgQWFhIY7jarW6/1ROTk4wYvMCm8P09PDmXhOHzzRqPbaAqLBp06bNmzefPn26oKDg4MGDnpJduXLlscces9vtW7duLS0tFYvFTqez/yyXyw1GbF7Qqxw8ofv71f1RnhA1G4NikMFgrF69evHixdu2bSstLU1LS5s0aVLfqYFf8r59++Lj43ft2oWiKEllQZ2+4uWHwX0ZFEiQMG5Q7uK+lgefz9+4cSMA4Nq1a/2ClMq7T6A6nS4tLa1Pn91uN5vNA8vgrxicPeDwxYhQ4v75wn0ZjJCHKdvtOqU9PJId2FCee+45gUAwbdq06upqAMC4ceMAAFlZWQiC7Ny5s6CgwGazLVu2rK9dUlFRIRaLy8vLDQZDS0uLp1I2OHtgY+5otjgx4Gn8BHnhhRfcnjBqMZMei0kOcI3T3t5eXV198uRJi8Xy9NNP5+bmAgBEIpFcLj9z5syFCxcMBkN+fn5WVlZra+uBAwdqa2vnzZtXVFR06tSp9PR0qVT60UcfzZgxY/z48f3XHJw9sDH/+LVOnsSJTnL/fOGxf1DRamn81jCXqH/xt8Dxss4Zi2ViD70EHgebY1O4l09qbjeZE9Lc904bDIaCggK3p+Lj49vb2wcfnz179osvvkg6cj958sknm5ubBx8fN25cY2Pj4OMZGRlvvvmmp6s1XjaEcZme9BH0Uffctp4/qCzakuD2rNPp7Orqcn9RhvvLcrlciUTi6eMChVKpdDjcPIF5iorNZstkHrtBy/52Y9WfEzw1ZYh7+b85ohyVxkuaMESdNLDxc43ebMCnzo/wkoagyTKrMPLrw0qD2v1D9chG0WK5dsXoXR8gM9pps+J7/9wciBHE4YTF5Hj7Ly1kUpIaL7bb8Lf/s7lX76Ac2PCgp91a9vdWDHOSSUx21oelF99f2vbgo/K41BE+cNz8o7H2tHbln8j2kvk28+j8Zz0GreP+h2WyuDB/I4SXjhbLvyvV8sSwmYWR5HP5PPut7Zr5YqVqVDpPnsBJzuAjKMP3UOHCbnW21vd23bRqOu33PSyNSfLtMczPGZgtP/U2XTXeqDeNnSJkhTH5IpQvRjg8ZDhMYQUIk2E2YiYDZjLgvXpHe5MlJUOQli1ITPen0eanwX7arpm1PXaTATPpcafThdkDqRDH8bq6uv7ur0ARxmP2dTvzRYg0hk2xZqdqMKj09vbm5+dXVVWFOhBv0HP5qUIbpArsBvu6YGEGdoNu+6OgAnaDwRsCDhSwG9TpdKEOgQDYDcbGxoY6BAJgN6hQKEIdAgGwG8zMzAx1CATAbrCuri7UIRAAu0H4gd2gl1E0SIDdoErl7U0EGIDdYGSkD93FIQF2g0GdkRUQYDcIP7AbTE1NDXUIBMBu0O0cIqiA3SD8wG5w4ExLOIHdYENDQ6hDIAB2g/ADu0G6b4YqdN/MyAd2g/RoJ1Xo0c6RD+wG6fFiqtDjxVQZM2ZMqEMgAHaDv/zyS6hDIAB2g/ADu8HoaLJrUYYK2A16evkRHmA3mJGREeoQCIDdYH19fahDIAB2g3QZpApdBqmSkOD+DXt4gPGNnA0bNigUChRFnU6nSqWSyWRMJtPhcJw4cSLUobkBxjK4Zs0ag8HQ0dHR2dnpcDg6Ozs7OjoQJCgrqVEHRoO5ubm/ehx2uVzQDpjAaBAAsHbtWh7v7guDMTExK1euDGlEHoHU4Jw5c5KTk/vr6KysrIkTJ4Y6KPdAahAAsH79+r7uVZlMBm0BhNpgbm5uSkpK35AxtJWgP/s09eowTZcdw4aiDbRk/lM27WeLcte31puG4ONYbIY0hu1leSO3+NAe1HTZq79UqTpsieMEJn1Q1ncMLVwhcqvRFJPEeaAoiisg23gia1CnclS+rchbGysQD91C6SFB02W7cLircFMcX0SqMJKqB+0254EdbUv+mDji9QEAIqLDFj4eX/6PNpLpSZXBCxUqsSwsOeM3tMtBQ40ORV1T5hKvVEeqDCqaLcKIkV/6BiKUsDpbSS30T6414wLCiAAvyAo5YikLc5D6hSBl0KhzgGGxFk/gcDqB2YCTSQlvi3q4QBukCm2QKrRBqtAGqUIbpAptkCq0QarQBqlCG6QKbZAq8Bp8eVvJo+sCsFfL/gMffnbw40BE5B54DVLH6XTuK9v9zrtvBPVTfB5pGi4oOjtKd7xYX/9jsD8oKAYPffHpufOnH1m+pqxst1qjGjMm/dnNJaNGJfWdPX36ePn+9xWKdqlU9tCiwjWr1zOZd26Fc+dPf/jRO93dnUmJKQO3trJarfvKdn917qTdbkuIT1yxYu0Dc+Z7j+HSpa+ZDObO0ree2fJUMP7GfoJVBhsb6w8e/HjLlhIMw1577ZV//HPrnt0fAgBOnTq2vfSFuXMXPPH4Hxoa6t57fw8AYG3xEwCAs1+dfGVbyeRJ2SseKe7qUny6/4O4uIS+m/GvJc90dSnWrF4fHh7xww+1L738vNVqWbRwsZcA5uTOX7Z0lVLZE6Q/sJ8g3sWvvPx6RIQUALB06cq39ryuN+hFQtG+93ZnZk4qef5lAMCsmQ8YjYYDn324bOkqBEHe3L1z4sTJO0p3903T6ui43dzSBAD45sK5n+q+319eKZNFAgDy5i6wWMxfHN7v3aBUOkQLdgXRIIdzZ4F7uTwGAKBWKQ16nUqlLFqxtj/N1Kn3nfhXRXtHm8Gg1+t1y5et7p/lxvy/f9TUVGMYtrr47p5QOI7z+YLgRe4TQ/FLwkJZAADcidtMNgBAePjdXWeEQhEAQKXs0em1AIDoaDdrhmq1aqlU9trOvQMPIigsv4FDGkdUpBwAoNfffdlQq9X0ewQA6HTawbmEQpFOp5XLY8LCYNzSY0jbg1KpLFoec/nyxf4jX399lsPhpKaOHT06jclknv3qX4Nz3XNPDo7jX1Ye6j8SvA3H/WCo74V1jz21vfSFHTtfmjr1vqtXL1dfrHrs0d9xuVwul7twQcHxE0ftNltOznS1WvXtt9USiRQAMC9vUeWxw3vf/u/OLkXamPTm5qbqi+c/eO8QhzNEm6R7Z6gNPvhgvtVm/fxQ+ekzx2XSyN9teHpl0aN9p57+45/YbPbZr07WfleTkTFp9Og0jUYNAGCxWDv+ufvdfW+cO3fq2LHD8fGjCh5ejkJTD5Ka9bGvpHXJpsQwHqRzwYOBrsd+4Yuu1X8h3q0Jlm/SD97d9+bAyrEfkVBc/knFkIUxjA2uWLE2P3/p4ONMxpD+PA5jg2KRWCwShzqKEd27NTTQBqlCG6QKbZAqtEGq0AapQhukCm2QKrRBqtAGqULKYFQcx/lbm8vvckmiSb0AQq4MMoG600Y1qGGFqsPK5pCSQypRSiZfpbBSjmo4oe2yJ08gta8xKYMZ08UGpb2hBvblKAPFd2dVKAukZJIaUPXh/eLKdxThUWHhUWGyuDAGY9hv3z4YJ+5StluV7RZ2GHPWUrID9r6t2NNw2XDzZ5MTB6qOIakWXS6b3T5kg5yy2DBWGGP0JEHqRB+G82Fc86gfehfy3wS0QarAbhDmdVL6gN0gvbsGVejd1qhC77ZGFXp/EqrQ+5NQha4HqULXgyMf2A2OHTs21CEQALvB69evhzoEAmA3CD+wG4RkurkXYDdotcI+PgO7QbE49LNUvQO7Qb1eH+oQCIDdIPzAbjA+Pj7UIRAAu8H29vZQh0AA7AbhB3aD9K6TVKF3nRz5wG6QHu2kCj3aOfKB3SA9TkIVepyEKhIJ8e4MoQV2g1qtmxVooAJ2g/ADu0F61gdV6FkfVBk/fnyoQyAAdoMNDQ2hDoEA2A3SZZAqdBmkyoQJE0IdAgEwvpGzadMmjUbDYrFwHG9paUlJSUFRFMfx8vLyUIfmBhhXjZo9e/arr76K43e26mpqaurbRjvUcbkHxrt4xYoVCQkJvzqYk5MTonAIgNEgAKC4uHjgC4kikWjVqlUhjcgjkBpcsmRJXFxc/3/HjBkza9askEbkEUgNAgBWrVrVVwzFYnFxcXGow/EIvAYLCwv7iuHo0aNnzpwZ6nA8EpTfYrMBw0nteUlA0bJ1ZWVlRcvWGbUB2LIbRRlcYeCXkg1Me7D7lrW13qTudHTesNjMuCSaY+2FbptylM00auwcPhIzmhsVx07J4EtjA/D2PFWDP13QNV7ptVpc/AieQMpD2QgaBu+SwS6XC7PjmA3vVZlMarNYyhqXI0ifKqJyTf8NNl01fnNEJYriS0aJWWwYW+aE2K2Y5qbWbrbNLpQljuf7dxE/DR5/v8dsBuGxYhZnWLobiLXXbuw2yGLQOcukfmT3x+CBnbe5EoE4llLhhw1NmxYB9sVPudmcwjs+GzzyloIlEgmkXF8/CX60CoOA45i3JsqnXL61B4/s7mCJBCNSHwBAEisyWVlnyrt9yuWDweoKFWBzBFI/a9xhQXisSKcFP3ztwyA1WYM9bdaWOrMkPtzf2IYNkaNll0/pTAay7VmyBi8cVUuTIkgkHAnIUyXVR1UkE5My2HbdbHcwRmr1NxhxjLDntp3kkoukDP74jZ4nhWVvrl/xX6X5hyq2B/yyPJmg7qKBTEpSBm81mkRRpJYzHDEII/mtdSYyKYkN3mwwhcu5I3LBQS+wuSgDYaoUxDcy8TNZz20rRxysGrC59bsTZ95SdDUJBRGpydkL5/1eJJQBAEpembvs4efqG6sarl/kcgTTphbOn/NkXxYcx89WldXUHrXbLaNTpjgcwXp9lh/B6b5llRH13xCXQYMaYyJB6Yj9peXKux/9hzwqecWSv86avrr15vd7399kt98xcuDwi7HRaX94Yu89WQtPn3u34fqdPdqOHNtxpqosPW16Yf6zbBbHYjUGIzYAAIPBJNMvSVwGe3U4SxCUDqujx1+dll1YmP9s33/TUu/d8T9F15trMsfnAgBy7imYO3sdACA2Ou3ydxVNzTXjx97frrhWU3tk7uz1C/M2AgCyJz/UcuNqMGIDACBstFdvJ0xGbBBlM5EgdPlptJ3dyhsqze2a2qMDj+v0dx6q2Ow7VQeCIGJRlN6gBADUNVQBAGZNvztuxwjarkwsDgIAce1PbBBzOJ02POAVobFXDQCYN+fJiePnDDwuFLpZQpbJRJ1OHACg03VxOAI+byhefHdYMa6AuNuF2CBfjBpNgRj1+P9wOUIAgMNhi4pMIp+Lz5dYrb0OzM5CSS1YTgXMhgvjiG8+4lsgPBJ1DdjOOlBEykaFi6OvXK202e9swonjGIY5vOeKj0sHAHz/06mAx+MOlzCCRC1HmCI6kXOtViMdFeAbh8FgLF70zIf7n3vj7Sfuy1nqdOK135+YMmnBwDpuMFkT8s5WvfdFxfau7ta4mLSbt+sMRmVgA+vHqDTHJBP/1cRlMCGNZ1TbnHjgi2Hm+NzHi19DENaXJ14/W/WeRBKdkjTZexYEQZ5cuyst9d5/X/ni2Kk3mAwmnxeU7iKbyYEwgUROXFeQ6qM+/l6XA3DDYyB9NA4Gqpt6eTQ+szCSMCWpcaJ75ojPfKryYvB687cff/b84OMsNMyBuX8wenrDPnlUMplPJ0Pj9Yvlh/4++LjL5QLA5bbF8/v1b8XFelwWTddhmF8U5+nsQMiOkxzdo2DyhJ76F+x2a69JM/g4hjlQlOU2i1gUhSABG+fzFIDT6XS5XP17ww9EJIz0FJu23SASOOauIjVgQtagustW+W53Ujapr2W403Th1mMlZHcbJtugl0aHjcsRqFrdfM8jjM5rPTMWy8hv1uzDI9G9D0ZwObiuM1hP8jCgvqWLTUTH3+vDULjP48UnPui24RxJ7Aj8XVbe0EXHg5kFvs1c8PmxfNE6OcNuUreNtP1yeprVYhHmqz7/581UV6gUtzBhtIgrHKLtV4KHSWs1qwypE7mTc/1pnPs/d+tWo/mbIyqEzYpIDOcIgv6cHwwsBrv6hobFds1eJo1O9LP7ier8waarxrpLRm23XRjJ48t4KAthhSEIC9IphH2TBzEHZuwxG5Xm6CTOxBmiJH/nvfURmDmserXjRp2pq83W3Wa19uJcIWo2QjeHlcVi4piTI0CjkzixSWHJmXy+KABN+qC8FYbZXTgO3StIKIuBoIEfcYTxvbrhBbxvQwwXaINUoQ1ShTZIFdogVWiDVPlfIhWXbCnRiFsAAAAASUVORK5CYII=",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from typing_extensions import TypedDict\n",
    "from IPython.display import Image, display\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "\n",
    "class State(TypedDict):\n",
    "    foo: int\n",
    "\n",
    "def node_1(state):\n",
    "    print(\"---Node 1---\")\n",
    "    return {\"foo\": state['foo'] + 1}\n",
    "\n",
    "# Build graph\n",
    "builder = StateGraph(State)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "69634df1-4f02-446f-b5cf-6a83d1e15e37",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---Node 1---\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'foo': 2}"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.invoke({\"foo\" : 1})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "775a099c-c41c-412f-8f05-e7436388ae79",
   "metadata": {},
   "source": [
    "Let's look at the state update, `return {\"foo\": state['foo'] + 1}`.\n",
    "\n",
    "As discussed before, by default LangGraph doesn't know the preferred way to update the state.\n",
    " \n",
    "So, it will just overwrite the value of `foo` in `node_1`: \n",
    "\n",
    "```\n",
    "return {\"foo\": state['foo'] + 1}\n",
    "```\n",
    " \n",
    "If we pass `{'foo': 1}` as input, the state returned from the graph is `{'foo': 2}`.\n",
    "\n",
    "## Branching\n",
    "\n",
    "Let's look at a case where our nodes branch."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2b8d6ad4-2991-4325-933d-67057bc150f4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "class State(TypedDict):\n",
    "    foo: int\n",
    "\n",
    "def node_1(state):\n",
    "    print(\"---Node 1---\")\n",
    "    return {\"foo\": state['foo'] + 1}\n",
    "\n",
    "def node_2(state):\n",
    "    print(\"---Node 2---\")\n",
    "    return {\"foo\": state['foo'] + 1}\n",
    "\n",
    "def node_3(state):\n",
    "    print(\"---Node 3---\")\n",
    "    return {\"foo\": state['foo'] + 1}\n",
    "\n",
    "# Build graph\n",
    "builder = StateGraph(State)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "builder.add_node(\"node_2\", node_2)\n",
    "builder.add_node(\"node_3\", node_3)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", \"node_2\")\n",
    "builder.add_edge(\"node_1\", \"node_3\")\n",
    "builder.add_edge(\"node_2\", END)\n",
    "builder.add_edge(\"node_3\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "106729b3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---Node 1---\n",
      "---Node 2---\n",
      "---Node 3---\n",
      "InvalidUpdateError occurred: At key 'foo': Can receive only one value per step. Use an Annotated key to handle multiple values.\n",
      "For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE\n"
     ]
    }
   ],
   "source": [
    "from langgraph.errors import InvalidUpdateError\n",
    "try:\n",
    "    graph.invoke({\"foo\" : 1})\n",
    "except InvalidUpdateError as e:\n",
    "    print(f\"InvalidUpdateError occurred: {e}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b9717ccd-3d34-476a-8952-e6a7629ebefe",
   "metadata": {},
   "source": [
    "We see a problem! \n",
    "\n",
    "Node 1 branches to nodes 2 and 3.\n",
    "\n",
    "Nodes 2 and 3 run in parallel, which means they run in the same step of the graph.\n",
    "\n",
    "They both attempt to overwrite the state *within the same step*. \n",
    "\n",
    "This is ambiguous for the graph! Which state should it keep? "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1609cf7-dc47-4926-a154-77904b6cc550",
   "metadata": {},
   "source": [
    "## Reducers\n",
    "\n",
    "[Reducers](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) give us a general way to address this problem.\n",
    "\n",
    "They specify how to perform updates.\n",
    "\n",
    "We can use the `Annotated` type to specify a reducer function. \n",
    "\n",
    "For example, in this case let's append the value returned from each node rather than overwriting them.\n",
    "\n",
    "We just need a reducer that can perform this: `operator.add` is a function from Python's built-in operator module.\n",
    "\n",
    "When `operator.add` is applied to lists, it performs list concatenation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "103d808c-55ec-44f2-a688-7b5e1572875a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from operator import add\n",
    "from typing import Annotated\n",
    "\n",
    "class State(TypedDict):\n",
    "    foo: Annotated[list[int], add]\n",
    "\n",
    "def node_1(state):\n",
    "    print(\"---Node 1---\")\n",
    "    return {\"foo\": [state['foo'][0] + 1]}\n",
    "\n",
    "# Build graph\n",
    "builder = StateGraph(State)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9e68cdff-f6e1-4de5-a7bf-6ca0cfee19bf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---Node 1---\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'foo': [1, 2]}"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.invoke({\"foo\" : [1]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63fbd6e0-0207-4049-b86d-c006cbba630b",
   "metadata": {},
   "source": [
    "Now, our state key `foo` is a list.\n",
    "\n",
    "This `operator.add` reducer function will append updates from each node to this list. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "768fd0ed-5e24-44a4-b14d-0e299310e105",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def node_1(state):\n",
    "    print(\"---Node 1---\")\n",
    "    return {\"foo\": [state['foo'][-1] + 1]}\n",
    "\n",
    "def node_2(state):\n",
    "    print(\"---Node 2---\")\n",
    "    return {\"foo\": [state['foo'][-1] + 1]}\n",
    "\n",
    "def node_3(state):\n",
    "    print(\"---Node 3---\")\n",
    "    return {\"foo\": [state['foo'][-1] + 2]}\n",
    "\n",
    "# Build graph\n",
    "builder = StateGraph(State)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "builder.add_node(\"node_2\", node_2)\n",
    "builder.add_node(\"node_3\", node_3)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", \"node_2\")\n",
    "builder.add_edge(\"node_1\", \"node_3\")\n",
    "builder.add_edge(\"node_2\", END)\n",
    "builder.add_edge(\"node_3\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5439baad-5a75-4188-b936-dbe74cdd9078",
   "metadata": {},
   "source": [
    "We can see that updates in nodes 2 and 3 are performed concurrently because they are in the same step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "44598f97-0a59-4ed4-9d9a-e15a98b3d8fb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---Node 1---\n",
      "---Node 2---\n",
      "---Node 3---\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'foo': [1, 2, 3, 4]}"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.invoke({\"foo\" : [1]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87faaa07-2955-4466-9bca-4b536e05f260",
   "metadata": {},
   "source": [
    "Now, let's see what happens if we pass `None` to `foo`.\n",
    "\n",
    "We see an error because our reducer, `operator.add`, attempts to concatenate `NoneType` pass as input to list in `node_1`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "7f05984b-2bc7-48d1-b070-c8a001a6b59a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TypeError occurred: can only concatenate list (not \"NoneType\") to list\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    graph.invoke({\"foo\" : None})\n",
    "except TypeError as e:\n",
    "    print(f\"TypeError occurred: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f9d4930-ee8f-4ffc-b9e1-3c910b2e15f6",
   "metadata": {},
   "source": [
    "## Custom Reducers\n",
    "\n",
    "To address cases like this, [we can also define custom reducers](https://langchain-ai.github.io/langgraph/how-tos/subgraph/#custom-reducer-functions-to-manage-state). \n",
    "\n",
    "For example, lets define custom reducer logic to combine lists and handle cases where either or both of the inputs might be `None`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "3314219d-29ff-4b78-b18e-fa9f7878a02f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def reduce_list(left: list | None, right: list | None) -> list:\n",
    "    \"\"\"Safely combine two lists, handling cases where either or both inputs might be None.\n",
    "\n",
    "    Args:\n",
    "        left (list | None): The first list to combine, or None.\n",
    "        right (list | None): The second list to combine, or None.\n",
    "\n",
    "    Returns:\n",
    "        list: A new list containing all elements from both input lists.\n",
    "               If an input is None, it's treated as an empty list.\n",
    "    \"\"\"\n",
    "    if not left:\n",
    "        left = []\n",
    "    if not right:\n",
    "        right = []\n",
    "    return left + right\n",
    "\n",
    "class DefaultState(TypedDict):\n",
    "    foo: Annotated[list[int], add]\n",
    "\n",
    "class CustomReducerState(TypedDict):\n",
    "    foo: Annotated[list[int], reduce_list]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dcdea26a-38d0-4faf-9bf6-cd52eb902635",
   "metadata": {},
   "source": [
    "In `node_1`, we append the value 2."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "f5f270db-6eff-47c9-853b-dfb8108ff28c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGsAAADqCAIAAAAqMSwmAAAAAXNSR0IArs4c6QAAFXxJREFUeJztnXtcE1e+wE8yk5B3CAmEpzxERAXRitRaH1jRqqUoasUHttrWrbtu7+dT7W73dtm1vW1dF23rva3VttI31VqrUtT1VaUVXarUPqCgFFARwiPvhLxnkvsHXuSWJDPJJORA5/uXzpwz+fHNmZMz55w5h+FyuQANBZihDmDYQxukCm2QKrRBqtAGqUIbpApKMb9R49CrHWYjbjbgmMPldA6DthGCAhRl8kQIT4hKolk8ASUJDP/ag+pOW8tPpht1JjaPAVwMnhDhiRAuH3Xiw8AgymL0GjCzATcbMZvFyWIzUzL5qVkCkZTlx9V8Ntirwy5VqlwAhMtYyZn8qHiOH58KFZ03LK11Jm23XSBBp+fL2BzfajbfDF45ram/pJ/+sGzsFKHvocJOXbX+0jHVtIekWTPDyefywWDFno7UyYIJ08T+Rjg8+O6sRt1ln18cTTI92RJb9rcbkx+QjHh9AIApeRGJ6fyKPR1kM7hIsK+kVaWwkkk5YvjlB+OBnW1kUhLfxRV7OiY/IBk1lheA73dY0fitoaPVkrdK7j0ZgcHaMxquAJlw38i/ed1Se1bD5RP8+d7qwV4dVndR/5vVBwDIzos4f1DpPY03g5cqVdMflgU6qmHGffnSS5UqLwk8GlR32lwAjMh2n09MmStRKWxWE+YpgUeDLT+ZwmX+POX4R319vc1mC1V27/BFaGu92dNZjwZv1JmSM/lBiulXVFZWrlu3zmKxhCQ7ISmZgta6Xk9n3Rs0aBxhPOaQPfP6XXz6GhLBK319JGfwe7WYp24nDwbVjiAN4d26dWvjxo0zZsxYtGjRtm3bnE5nZWXl9u3bAQB5eXnZ2dmVlZUAgO7u7q1bt+bl5U2bNq2oqOjkyZN92XU6XXZ29scff1xSUjJjxowNGza4zR5wMIdLr3K4PeW+a8xsxHlCJBihvPTSSzdv3tyyZYvJZKqtrWUymffff39xcfEnn3yya9cugUAwatQoAACGYT///PPy5cvDw8PPnTtXUlKSkJAwYcKEvouUlZU98sgje/fuRRBELpcPzh5weCLEbMAlUW5OeTBowHmioBhUKBTp6emFhYUAgOLiYgBAREREfHw8ACAjIyM8/E6nSFxc3Oeff85gMAAAixcvzsvLq6qq6jeYmZm5adOm/msOzh5w+CLUZHD/c+zxl4TFDsoAwKJFi2pqakpLSzUajfeUTU1NmzdvXrBgQWFhIY7jarW6/1ROTk4wYvMCm8P09PDmXhOHzzRqPbaAqLBp06bNmzefPn26oKDg4MGDnpJduXLlscces9vtW7duLS0tFYvFTqez/yyXyw1GbF7Qqxw8ofv71f1RnhA1G4NikMFgrF69evHixdu2bSstLU1LS5s0aVLfqYFf8r59++Lj43ft2oWiKEllQZ2+4uWHwX0ZFEiQMG5Q7uK+lgefz9+4cSMA4Nq1a/2ClMq7T6A6nS4tLa1Pn91uN5vNA8vgrxicPeDwxYhQ4v75wn0ZjJCHKdvtOqU9PJId2FCee+45gUAwbdq06upqAMC4ceMAAFlZWQiC7Ny5s6CgwGazLVu2rK9dUlFRIRaLy8vLDQZDS0uLp1I2OHtgY+5otjgx4Gn8BHnhhRfcnjBqMZMei0kOcI3T3t5eXV198uRJi8Xy9NNP5+bmAgBEIpFcLj9z5syFCxcMBkN+fn5WVlZra+uBAwdqa2vnzZtXVFR06tSp9PR0qVT60UcfzZgxY/z48f3XHJw9sDH/+LVOnsSJTnL/fOGxf1DRamn81jCXqH/xt8Dxss4Zi2ViD70EHgebY1O4l09qbjeZE9Lc904bDIaCggK3p+Lj49vb2wcfnz179osvvkg6cj958sknm5ubBx8fN25cY2Pj4OMZGRlvvvmmp6s1XjaEcZme9BH0Uffctp4/qCzakuD2rNPp7Orqcn9RhvvLcrlciUTi6eMChVKpdDjcPIF5iorNZstkHrtBy/52Y9WfEzw1ZYh7+b85ohyVxkuaMESdNLDxc43ebMCnzo/wkoagyTKrMPLrw0qD2v1D9chG0WK5dsXoXR8gM9pps+J7/9wciBHE4YTF5Hj7Ly1kUpIaL7bb8Lf/s7lX76Ac2PCgp91a9vdWDHOSSUx21oelF99f2vbgo/K41BE+cNz8o7H2tHbln8j2kvk28+j8Zz0GreP+h2WyuDB/I4SXjhbLvyvV8sSwmYWR5HP5PPut7Zr5YqVqVDpPnsBJzuAjKMP3UOHCbnW21vd23bRqOu33PSyNSfLtMczPGZgtP/U2XTXeqDeNnSJkhTH5IpQvRjg8ZDhMYQUIk2E2YiYDZjLgvXpHe5MlJUOQli1ITPen0eanwX7arpm1PXaTATPpcafThdkDqRDH8bq6uv7ur0ARxmP2dTvzRYg0hk2xZqdqMKj09vbm5+dXVVWFOhBv0HP5qUIbpArsBvu6YGEGdoNu+6OgAnaDwRsCDhSwG9TpdKEOgQDYDcbGxoY6BAJgN6hQKEIdAgGwG8zMzAx1CATAbrCuri7UIRAAu0H4gd2gl1E0SIDdoErl7U0EGIDdYGSkD93FIQF2g0GdkRUQYDcIP7AbTE1NDXUIBMBu0O0cIqiA3SD8wG5w4ExLOIHdYENDQ6hDIAB2g/ADu0G6b4YqdN/MyAd2g/RoJ1Xo0c6RD+wG6fFiqtDjxVQZM2ZMqEMgAHaDv/zyS6hDIAB2g/ADu8HoaLJrUYYK2A16evkRHmA3mJGREeoQCIDdYH19fahDIAB2g3QZpApdBqmSkOD+DXt4gPGNnA0bNigUChRFnU6nSqWSyWRMJtPhcJw4cSLUobkBxjK4Zs0ag8HQ0dHR2dnpcDg6Ozs7OjoQJCgrqVEHRoO5ubm/ehx2uVzQDpjAaBAAsHbtWh7v7guDMTExK1euDGlEHoHU4Jw5c5KTk/vr6KysrIkTJ4Y6KPdAahAAsH79+r7uVZlMBm0BhNpgbm5uSkpK35AxtJWgP/s09eowTZcdw4aiDbRk/lM27WeLcte31puG4ONYbIY0hu1leSO3+NAe1HTZq79UqTpsieMEJn1Q1ncMLVwhcqvRFJPEeaAoiisg23gia1CnclS+rchbGysQD91C6SFB02W7cLircFMcX0SqMJKqB+0254EdbUv+mDji9QEAIqLDFj4eX/6PNpLpSZXBCxUqsSwsOeM3tMtBQ40ORV1T5hKvVEeqDCqaLcKIkV/6BiKUsDpbSS30T6414wLCiAAvyAo5YikLc5D6hSBl0KhzgGGxFk/gcDqB2YCTSQlvi3q4QBukCm2QKrRBqtAGqUIbpAptkCq0QarQBqlCG6QKbZAq8Bp8eVvJo+sCsFfL/gMffnbw40BE5B54DVLH6XTuK9v9zrtvBPVTfB5pGi4oOjtKd7xYX/9jsD8oKAYPffHpufOnH1m+pqxst1qjGjMm/dnNJaNGJfWdPX36ePn+9xWKdqlU9tCiwjWr1zOZd26Fc+dPf/jRO93dnUmJKQO3trJarfvKdn917qTdbkuIT1yxYu0Dc+Z7j+HSpa+ZDObO0ree2fJUMP7GfoJVBhsb6w8e/HjLlhIMw1577ZV//HPrnt0fAgBOnTq2vfSFuXMXPPH4Hxoa6t57fw8AYG3xEwCAs1+dfGVbyeRJ2SseKe7qUny6/4O4uIS+m/GvJc90dSnWrF4fHh7xww+1L738vNVqWbRwsZcA5uTOX7Z0lVLZE6Q/sJ8g3sWvvPx6RIQUALB06cq39ryuN+hFQtG+93ZnZk4qef5lAMCsmQ8YjYYDn324bOkqBEHe3L1z4sTJO0p3903T6ui43dzSBAD45sK5n+q+319eKZNFAgDy5i6wWMxfHN7v3aBUOkQLdgXRIIdzZ4F7uTwGAKBWKQ16nUqlLFqxtj/N1Kn3nfhXRXtHm8Gg1+t1y5et7p/lxvy/f9TUVGMYtrr47p5QOI7z+YLgRe4TQ/FLwkJZAADcidtMNgBAePjdXWeEQhEAQKXs0em1AIDoaDdrhmq1aqlU9trOvQMPIigsv4FDGkdUpBwAoNfffdlQq9X0ewQA6HTawbmEQpFOp5XLY8LCYNzSY0jbg1KpLFoec/nyxf4jX399lsPhpKaOHT06jclknv3qX4Nz3XNPDo7jX1Ye6j8SvA3H/WCo74V1jz21vfSFHTtfmjr1vqtXL1dfrHrs0d9xuVwul7twQcHxE0ftNltOznS1WvXtt9USiRQAMC9vUeWxw3vf/u/OLkXamPTm5qbqi+c/eO8QhzNEm6R7Z6gNPvhgvtVm/fxQ+ekzx2XSyN9teHpl0aN9p57+45/YbPbZr07WfleTkTFp9Og0jUYNAGCxWDv+ufvdfW+cO3fq2LHD8fGjCh5ejkJTD5Ka9bGvpHXJpsQwHqRzwYOBrsd+4Yuu1X8h3q0Jlm/SD97d9+bAyrEfkVBc/knFkIUxjA2uWLE2P3/p4ONMxpD+PA5jg2KRWCwShzqKEd27NTTQBqlCG6QKbZAqtEGq0AapQhukCm2QKrRBqtAGqULKYFQcx/lbm8vvckmiSb0AQq4MMoG600Y1qGGFqsPK5pCSQypRSiZfpbBSjmo4oe2yJ08gta8xKYMZ08UGpb2hBvblKAPFd2dVKAukZJIaUPXh/eLKdxThUWHhUWGyuDAGY9hv3z4YJ+5StluV7RZ2GHPWUrID9r6t2NNw2XDzZ5MTB6qOIakWXS6b3T5kg5yy2DBWGGP0JEHqRB+G82Fc86gfehfy3wS0QarAbhDmdVL6gN0gvbsGVejd1qhC77ZGFXp/EqrQ+5NQha4HqULXgyMf2A2OHTs21CEQALvB69evhzoEAmA3CD+wG4RkurkXYDdotcI+PgO7QbE49LNUvQO7Qb1eH+oQCIDdIPzAbjA+Pj7UIRAAu8H29vZQh0AA7AbhB3aD9K6TVKF3nRz5wG6QHu2kCj3aOfKB3SA9TkIVepyEKhIJ8e4MoQV2g1qtmxVooAJ2g/ADu0F61gdV6FkfVBk/fnyoQyAAdoMNDQ2hDoEA2A3SZZAqdBmkyoQJE0IdAgEwvpGzadMmjUbDYrFwHG9paUlJSUFRFMfx8vLyUIfmBhhXjZo9e/arr76K43e26mpqaurbRjvUcbkHxrt4xYoVCQkJvzqYk5MTonAIgNEgAKC4uHjgC4kikWjVqlUhjcgjkBpcsmRJXFxc/3/HjBkza9askEbkEUgNAgBWrVrVVwzFYnFxcXGow/EIvAYLCwv7iuHo0aNnzpwZ6nA8EpTfYrMBw0nteUlA0bJ1ZWVlRcvWGbUB2LIbRRlcYeCXkg1Me7D7lrW13qTudHTesNjMuCSaY+2FbptylM00auwcPhIzmhsVx07J4EtjA/D2PFWDP13QNV7ptVpc/AieQMpD2QgaBu+SwS6XC7PjmA3vVZlMarNYyhqXI0ifKqJyTf8NNl01fnNEJYriS0aJWWwYW+aE2K2Y5qbWbrbNLpQljuf7dxE/DR5/v8dsBuGxYhZnWLobiLXXbuw2yGLQOcukfmT3x+CBnbe5EoE4llLhhw1NmxYB9sVPudmcwjs+GzzyloIlEgmkXF8/CX60CoOA45i3JsqnXL61B4/s7mCJBCNSHwBAEisyWVlnyrt9yuWDweoKFWBzBFI/a9xhQXisSKcFP3ztwyA1WYM9bdaWOrMkPtzf2IYNkaNll0/pTAay7VmyBi8cVUuTIkgkHAnIUyXVR1UkE5My2HbdbHcwRmr1NxhxjLDntp3kkoukDP74jZ4nhWVvrl/xX6X5hyq2B/yyPJmg7qKBTEpSBm81mkRRpJYzHDEII/mtdSYyKYkN3mwwhcu5I3LBQS+wuSgDYaoUxDcy8TNZz20rRxysGrC59bsTZ95SdDUJBRGpydkL5/1eJJQBAEpembvs4efqG6sarl/kcgTTphbOn/NkXxYcx89WldXUHrXbLaNTpjgcwXp9lh/B6b5llRH13xCXQYMaYyJB6Yj9peXKux/9hzwqecWSv86avrr15vd7399kt98xcuDwi7HRaX94Yu89WQtPn3u34fqdPdqOHNtxpqosPW16Yf6zbBbHYjUGIzYAAIPBJNMvSVwGe3U4SxCUDqujx1+dll1YmP9s33/TUu/d8T9F15trMsfnAgBy7imYO3sdACA2Ou3ydxVNzTXjx97frrhWU3tk7uz1C/M2AgCyJz/UcuNqMGIDACBstFdvJ0xGbBBlM5EgdPlptJ3dyhsqze2a2qMDj+v0dx6q2Ow7VQeCIGJRlN6gBADUNVQBAGZNvztuxwjarkwsDgIAce1PbBBzOJ02POAVobFXDQCYN+fJiePnDDwuFLpZQpbJRJ1OHACg03VxOAI+byhefHdYMa6AuNuF2CBfjBpNgRj1+P9wOUIAgMNhi4pMIp+Lz5dYrb0OzM5CSS1YTgXMhgvjiG8+4lsgPBJ1DdjOOlBEykaFi6OvXK202e9swonjGIY5vOeKj0sHAHz/06mAx+MOlzCCRC1HmCI6kXOtViMdFeAbh8FgLF70zIf7n3vj7Sfuy1nqdOK135+YMmnBwDpuMFkT8s5WvfdFxfau7ta4mLSbt+sMRmVgA+vHqDTHJBP/1cRlMCGNZ1TbnHjgi2Hm+NzHi19DENaXJ14/W/WeRBKdkjTZexYEQZ5cuyst9d5/X/ni2Kk3mAwmnxeU7iKbyYEwgUROXFeQ6qM+/l6XA3DDYyB9NA4Gqpt6eTQ+szCSMCWpcaJ75ojPfKryYvB687cff/b84OMsNMyBuX8wenrDPnlUMplPJ0Pj9Yvlh/4++LjL5QLA5bbF8/v1b8XFelwWTddhmF8U5+nsQMiOkxzdo2DyhJ76F+x2a69JM/g4hjlQlOU2i1gUhSABG+fzFIDT6XS5XP17ww9EJIz0FJu23SASOOauIjVgQtagustW+W53Ujapr2W403Th1mMlZHcbJtugl0aHjcsRqFrdfM8jjM5rPTMWy8hv1uzDI9G9D0ZwObiuM1hP8jCgvqWLTUTH3+vDULjP48UnPui24RxJ7Aj8XVbe0EXHg5kFvs1c8PmxfNE6OcNuUreNtP1yeprVYhHmqz7/581UV6gUtzBhtIgrHKLtV4KHSWs1qwypE7mTc/1pnPs/d+tWo/mbIyqEzYpIDOcIgv6cHwwsBrv6hobFds1eJo1O9LP7ier8waarxrpLRm23XRjJ48t4KAthhSEIC9IphH2TBzEHZuwxG5Xm6CTOxBmiJH/nvfURmDmserXjRp2pq83W3Wa19uJcIWo2QjeHlcVi4piTI0CjkzixSWHJmXy+KABN+qC8FYbZXTgO3StIKIuBoIEfcYTxvbrhBbxvQwwXaINUoQ1ShTZIFdogVWiDVPlfIhWXbCnRiFsAAAAASUVORK5CYII=",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TypeError occurred: can only concatenate list (not \"NoneType\") to list\n"
     ]
    }
   ],
   "source": [
    "def node_1(state):\n",
    "    print(\"---Node 1---\")\n",
    "    return {\"foo\": [2]}\n",
    "\n",
    "# Build graph\n",
    "builder = StateGraph(DefaultState)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))\n",
    "\n",
    "try:\n",
    "    print(graph.invoke({\"foo\" : None}))\n",
    "except TypeError as e:\n",
    "    print(f\"TypeError occurred: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd21936b-62f1-4311-9ce5-2c7d08aa35bf",
   "metadata": {},
   "source": [
    "Now, try with our custom reducer. We can see that no error is thrown."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "867784bc-796c-4b1e-a4d3-2810395cf5e2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---Node 1---\n",
      "{'foo': [2]}\n"
     ]
    }
   ],
   "source": [
    "# Build graph\n",
    "builder = StateGraph(CustomReducerState)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))\n",
    "\n",
    "try:\n",
    "    print(graph.invoke({\"foo\" : None}))\n",
    "except TypeError as e:\n",
    "    print(f\"TypeError occurred: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7ebc65e-c185-4981-a6e7-20fe37d2f8fe",
   "metadata": {},
   "source": [
    "## Messages\n",
    "\n",
    "In module 1, we showed how to use a built-in reducer, `add_messages`, to handle messages in state.\n",
    "\n",
    "We also showed that [`MessagesState` is a useful shortcut if you want to work with messages](https://langchain-ai.github.io/langgraph/concepts/low_level/#messagesstate). \n",
    "\n",
    "* `MessagesState` has a built-in `messages` key \n",
    "* It also has a built-in `add_messages` reducer for this key\n",
    "\n",
    "These two are equivalent. \n",
    "\n",
    "We'll use the `MessagesState` class via `from langgraph.graph import MessagesState` for brevity.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "901e69e5-c4cb-4d58-82fb-3b7d968758e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated\n",
    "from langgraph.graph import MessagesState\n",
    "from langchain_core.messages import AnyMessage\n",
    "from langgraph.graph.message import add_messages\n",
    "\n",
    "# Define a custom TypedDict that includes a list of messages with add_messages reducer\n",
    "class CustomMessagesState(TypedDict):\n",
    "    messages: Annotated[list[AnyMessage], add_messages]\n",
    "    added_key_1: str\n",
    "    added_key_2: str\n",
    "    # etc\n",
    "\n",
    "# Use MessagesState, which includes the messages key with add_messages reducer\n",
    "class ExtendedMessagesState(MessagesState):\n",
    "    # Add any keys needed beyond messages, which is pre-built \n",
    "    added_key_1: str\n",
    "    added_key_2: str\n",
    "    # etc"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "287805e4-722a-4428-b040-2892b29de870",
   "metadata": {},
   "source": [
    "Let's talk a bit more about usage of the `add_messages` reducer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "c8f61350-4fe0-4a2b-bb24-9305afb3c668",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[AIMessage(content='Hello! How can I assist you?', additional_kwargs={}, response_metadata={}, name='Model', id='8ad97b81-b448-4a29-bfea-d9410ef6da66'),\n",
       " HumanMessage(content=\"I'm looking for information on marine biology.\", additional_kwargs={}, response_metadata={}, name='Lance', id='a10ed802-90ce-460d-9f15-747f54328323'),\n",
       " AIMessage(content='Sure, I can help with that. What specifically are you interested in?', additional_kwargs={}, response_metadata={}, name='Model', id='db6c6d42-09f5-421a-8bbb-6537f7b54d79')]"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langgraph.graph.message import add_messages\n",
    "from langchain_core.messages import AIMessage, HumanMessage\n",
    "\n",
    "# Initial state\n",
    "initial_messages = [AIMessage(content=\"Hello! How can I assist you?\", name=\"Model\"),\n",
    "                    HumanMessage(content=\"I'm looking for information on marine biology.\", name=\"Lance\")\n",
    "                   ]\n",
    "\n",
    "# New message to add\n",
    "new_message = AIMessage(content=\"Sure, I can help with that. What specifically are you interested in?\", name=\"Model\")\n",
    "\n",
    "# Test\n",
    "add_messages(initial_messages , new_message)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc492370-0502-43e6-87cc-181c60b3dbdb",
   "metadata": {},
   "source": [
    "So we can see that `add_messages` allows us to append messages to the `messages` key in our state.\n",
    "\n",
    "### Re-writing\n",
    "\n",
    "Let's show some useful tricks when working with the `add_messages` reducer.\n",
    "\n",
    "If we pass a message with the same ID as an existing one in our `messages` list, it will get overwritten!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "1f6f82fd-a5a8-4e98-80f6-bb058f2acc47",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[AIMessage(content='Hello! How can I assist you?', additional_kwargs={}, response_metadata={}, name='Model', id='1'),\n",
       " HumanMessage(content=\"I'm looking for information on whales, specifically\", additional_kwargs={}, response_metadata={}, name='Lance', id='2')]"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Initial state\n",
    "initial_messages = [AIMessage(content=\"Hello! How can I assist you?\", name=\"Model\", id=\"1\"),\n",
    "                    HumanMessage(content=\"I'm looking for information on marine biology.\", name=\"Lance\", id=\"2\")\n",
    "                   ]\n",
    "\n",
    "# New message to add\n",
    "new_message = HumanMessage(content=\"I'm looking for information on whales, specifically\", name=\"Lance\", id=\"2\")\n",
    "\n",
    "# Test\n",
    "add_messages(initial_messages , new_message)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f06e7788-7054-4752-99fe-27ebb901f263",
   "metadata": {},
   "source": [
    "### Removal\n",
    "\n",
    "`add_messages` also [enables message removal](https://langchain-ai.github.io/langgraph/how-tos/memory/delete-messages/). \n",
    "\n",
    "For this, we simply use [RemoveMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.modifier.RemoveMessage.html) from `langchain_core`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "67ac97e5-efe2-40bc-9fe3-fd4f50922b8b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='1'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='2')]\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import RemoveMessage\n",
    "\n",
    "# Message list\n",
    "messages = [AIMessage(\"Hi.\", name=\"Bot\", id=\"1\")]\n",
    "messages.append(HumanMessage(\"Hi.\", name=\"Lance\", id=\"2\"))\n",
    "messages.append(AIMessage(\"So you said you were researching ocean mammals?\", name=\"Bot\", id=\"3\"))\n",
    "messages.append(HumanMessage(\"Yes, I know about whales. But what others should I learn about?\", name=\"Lance\", id=\"4\"))\n",
    "\n",
    "# Isolate messages to delete\n",
    "delete_messages = [RemoveMessage(id=m.id) for m in messages[:-2]]\n",
    "print(delete_messages)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "2d250578-3ec0-452e-91c0-072d785d96db",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[AIMessage(content='So you said you were researching ocean mammals?', additional_kwargs={}, response_metadata={}, name='Bot', id='3'),\n",
       " HumanMessage(content='Yes, I know about whales. But what others should I learn about?', additional_kwargs={}, response_metadata={}, name='Lance', id='4')]"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add_messages(messages , delete_messages)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5db095c5-6d9a-4e62-a097-0403797511f6",
   "metadata": {},
   "source": [
    "We can see that mesage IDs 1 and 2, as noted in `delete_messages` are removed by the reducer.\n",
    "\n",
    "We'll see this put into practice a bit later."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "c8b0347d-cbf0-4164-9cf6-39c4e040a313",
   "metadata": {},
   "outputs": [],
   "source": [
    "#FINISHED\n",
    "#Can define custom reducers for special cases\n",
    "#Using custom Typedict with Annotated messages list is same as using MessagesState\n",
    "#can do a trick with deleting messages using ids and the add_messages reducer\n",
    "#along with a defined delete_messages for loop -- the above example works to delete\n",
    "#all except to two latest messages"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
